package npu.zunsql.common;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;


public abstract class FormatObject {

    private volatile List<Object> fieldValues = null;

    private List<Object> valueList() {
        if (fieldValues == null) {
            fieldValues = Collections.unmodifiableList(toValueList(this, getValueFields(getClass())));
        }
        return fieldValues;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        return valueList().equals(((FormatObject) obj).valueList());
    }

    @Override
    public int hashCode() {
        return valueList().hashCode();
    }

    @Override
    public String toString() {
        StringBuilder buf = new StringBuilder(getClass().getSimpleName());
        buf.append(" {");
        Field[] fields = getValueFields(getClass());
        int i = 0;
        for (Object value : valueList()) {
            if (i > 0) {
                buf.append(", ");
            }
            buf.append(fields[i++].getName()).append('=').append(value);
        }
        buf.append('}');
        return buf.toString();
    }


    private static final Comparator<Field> NAME_ORDER = new Comparator<Field>() {
        @Override
        public int compare(Field field1, Field field2) {
            return field1.getName().compareTo(field2.getName());
        }
    };

    private static final ConcurrentMap<Class<?>, Field[]> valueFieldMap =
            new ConcurrentHashMap<Class<?>, Field[]>();

    private static Field[] getValueFields(Class<?> type) {
        Field[] fields = valueFieldMap.get(type);
        if (fields == null) {
            fields = introspectValueFields(type);
            valueFieldMap.put(type, fields);
        }
        return fields;
    }

    private static List<Object> toValueList(Object obj, Field[] fields) {
        List<Object> list = new ArrayList<Object>();
        for (Field field : fields) {
            try {
                list.add(field.get(obj));
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
        return list;
    }

    private static Field[] introspectValueFields(Class<?> type) {
        if (type == FormatObject.class) {
            return NO_FIELD;
        }
        List<Field> fieldList = new ArrayList<Field>();
        fieldList.addAll(Arrays.asList(introspectValueFields(type.getSuperclass())));
        List<Field> myFields = tail(fieldList);
        for (Field field : type.getDeclaredFields()) {
            if (!Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers())) {
                myFields.add(field);
            }
        }
        Collections.sort(myFields, NAME_ORDER);
        Field[] fields = fieldList.toArray(new Field[fieldList.size()]);
        AccessibleObject.setAccessible(fields, true);
        return fields;
    }

    private static <T> List<T> tail(List<T> list) {
        int size = list.size();
        return list.subList(size, size);
    }

    private static final Field[] NO_FIELD = new Field[0];
}

